package com.iqunxing.redis.redisInAction.ch08.util;

import java.util.UUID;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import com.google.common.base.Objects;

@Service
public class Lock {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 加锁.
     *
     * @param lockName
     *            the lock name
     * @return the string
     */
    public String acquireLock(String lockName) {
        return acquireLock(lockName, 3000, 10);
    }

    /**
     * 加锁.
     *
     * @param lockName
     *            the lock name
     * @param acquireTimeout
     *            the acquire timeout
     * @return the string
     */
    public String acquireLock(String lockName, long acquireTimeout, final long lockExpire) {
        final String id = UUID.randomUUID().toString();
        final String lockKey = "lock:" + lockName;
        long end = System.currentTimeMillis() + acquireTimeout;
        while (System.currentTimeMillis() < end) {
            String result = redisTemplate.execute(new RedisCallback<String>() {

                @Override
                public String doInRedis(RedisConnection conn) throws DataAccessException {
                    if (conn.setNX(lockKey.getBytes(), id.getBytes())) {
                        conn.expire(lockKey.getBytes(), lockExpire);
                        return id;
                    }
                    if (conn.ttl(lockKey.getBytes()) == -1) {
                        conn.expire(lockKey.getBytes(), lockExpire);
                    }
                    return null;
                }
            });
            if (StringUtils.isBlank(result)) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            } else {
                return result;
            }
        }
        return null;
    }

    /**
     * 释放锁.
     *
     * @param lockName
     *            the lock name
     * @param id
     *            the id
     * @return the string
     */
    public boolean releaseLock(String lockName, final String id) {
        final String lockKey = "lock:" + lockName;
        Boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {

            @Override
            public Boolean doInRedis(RedisConnection conn) throws DataAccessException {
                // 防止多个服务器处理同一个key需要watch操作(相当于是禁止了其他client处理这个key)
                conn.watch(lockKey.getBytes());
                // 如果锁没有过期,则锁的值必然没有改变
                if (Objects.equal(id, new String(conn.get(lockKey.getBytes())))) {
                    // 删除锁
                    conn.del(lockKey.getBytes());
                    return true;
                }
                // 锁已经过期了，释放watch操作
                conn.unwatch();
                return false;
            }
        });
        return result;
    }

}
